package controller;

import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;

import javax.swing.JOptionPane;

import util.Constants;
import util.MoveType;
import util.RunMode;
import util.SelectionMode;
import dao.BoardMementoDAO;
import entity.Board;
import entity.BoardMemento;
import entity.Edge;
import entity.Movable;
import entity.Move;
import entity.Poem;
import entity.Row;
import entity.Word;
import gui.BoardApplication;

/**
 * This is the move controller.
 */
public class MoveController implements MouseListener, MouseMotionListener {
	public Board board;
	private BoardApplication gui;
	private Movable item;

	private int offsetX;
	private int offsetY;
	private int originalX;
	private int originalY;

	private boolean snapped;
	private Edge snappedToEdge;
	private Movable snappedToItem;

	private SwapRequestController srController;

	/**
	 * Constructor of move controller.
	 */
	public MoveController(BoardApplication gui, Board board, RunMode mode){
		this.board = board;
		this.gui = gui;
		gui.setRunMode(mode);

		srController = new SwapRequestController(gui, board);
		srController.updateComboBoxes();
		srController.cleanOutboundArea();
	}

	/**
	 * Select an object. 
	 */
	public void select(int x, int y, SelectionMode s){
		item = board.getMovableAt(x, y, s);

		if(item == null){
			System.out.println("No item was selected.");
			return;
		}

		System.out.println("Selected item: " + item);

		originalX = item.getX();
		originalY = item.getY();
		offsetX = originalX - x; 
		offsetY = originalY - y;
	}

	/**
	 * Drag an object. 
	 */
	public void drag(int x, int y) {
		if(item == null){
			return;
		}

		if(item instanceof Word){ 
			Poem poemFrom = board.poemThatContains((Word) item); 
			Row rowFrom = poemFrom != null ? poemFrom.rowThatContains((Word) item) : null;

			if(rowFrom != null){
				int index = rowFrom.getWords().indexOf(item);

				if(index != 0 && index != rowFrom.getWords().size() - 1){
					return;
				}
			}
		}

		int newX = x + offsetX;
		int newY = y + offsetY;

		newX = newX >= 0 ? newX : 0;
		newX = newX + item.getWidth() <= Constants.BOARD_WIDTH ? newX : Constants.BOARD_WIDTH - item.getWidth();
		newY = newY >= 0 ? newY : 0;

		if(!(item instanceof Poem)){
			newY = newY + item.getHeight() <= Constants.UNPROTECTED_AREA_HEIGHT + Constants.PROTECTED_AREA_HEIGHT ? newY : Constants.UNPROTECTED_AREA_HEIGHT + Constants.PROTECTED_AREA_HEIGHT - item.getHeight();
		}

		item.setPosition(newX, newY);
		snap();
	}

	/**
	 * Release function. 
	 */
	public void release(int x, int y) {
		boolean checkpoint = true;

		if(item == null || item.getX() == originalX && item.getY() == originalY){
			return;
		}

		if(item instanceof Word && board.getProtectedWords().contains((Word) item)){
			Poem poemFrom = board.poemThatContains((Word) item);
			Row rowFrom = poemFrom != null ? poemFrom.rowThatContains((Word) item) : null;

			if(rowFrom != null){
				DisconnectController dc = new DisconnectController(gui, board);
				dc.disconnect((Word) item, checkpoint);
				checkpoint = false;
			}
		}else if(item instanceof Row){
			Row r = (Row) item;
			Poem p = board.poemThatContains((Row) item);
			Row prev = p.previousRow(r);
			Row next = p.nextRow(r);

			int prevX = prev != null ? prev.getX() : 0;
			int prevWidth = prev != null ? prev.getWidth() : Constants.BOARD_WIDTH;
			int nextX = next != null ? next.getX() : 0;
			int nextWidth = next != null ? next.getWidth() : Constants.BOARD_WIDTH;

			int borderLeft = Math.max(prevX, nextX);
			int borderRight = Math.min(prevX + prevWidth, nextX + nextWidth);

			if(item.getY() != originalY
					|| (item.getX() + item.getWidth() < borderLeft && item.getX() > borderRight)){
				DisconnectController dc = new DisconnectController(gui, board);
				dc.disconnect((Row) item, checkpoint);
				checkpoint = false;
			}else{
				((Row) item).setOffsetX(item.getX() - p.getX());
				p.rearrange();

				snapped = false;
			}
		}

		if(item.getY() < Constants.PROTECTED_AREA_HEIGHT - item.getHeight()){
			for(Word w : board.getProtectedWords()){
				if(item.checkCollision(w)){
					item.setPosition(originalX, originalY);
					return;
				}
			}

			if(item instanceof Word){
				if(srController.isSelected((Word) item)){
					srController.showProtectError();
					item.setPosition(originalX, originalY);
					return;
				}

				if(!board.getProtectedWords().contains(item)){
					ProtectController pc = new ProtectController(gui, board);
					boolean protectionSucceded = pc.protectWord((Word) item);

					if(!protectionSucceded){
						item.setPosition(originalX, originalY);
						return;
					}
				}
			}
		}else if(item.getY() >= Constants.PROTECTED_AREA_HEIGHT){
			ReleaseController rc = new ReleaseController(gui, board);
			rc.release(item, checkpoint);
		}

		Move move = new Move(MoveType.DEFAULT, checkpoint);
		checkpoint = false;

		Movable oldItem = item.clone();
		oldItem.setPosition(originalX, originalY);

		move.addOldItem(oldItem);
		move.addNewItem(item);

		board.addMove(move);

		if(snapped){
			ConnectController cc = new ConnectController(gui, board);
			cc.connect(snappedToItem, item, snappedToEdge, checkpoint);
			checkpoint = false;
		}

		BoardMemento bm = new BoardMemento(board);
		BoardMementoDAO dao = new BoardMementoDAO();
		dao.saveBoardMemento(bm, gui.getRunMode(), BoardMementoDAO.FILE_NAME);
	}

	/**
	 * Bring an object to front.
	 */
	public void hover(int x, int y) {
		Movable m = board.getMovableAt(x, y, SelectionMode.DEFAULT);
		board.bringToFront(m);
	}

	public boolean snap(){
		snapped = false;
		snappedToEdge = null;
		snappedToItem = null;

		int newX = item.getX();
		int newY = item.getY();

		for(Word w : board.getProtectedWords()){
			if(w != item && Math.abs(newY - w.getY()) <= w.getHeight()){
				if(Math.abs(newX - (w.getX() + w.getWidth())) <= 10){
					newX = (w.getX() + w.getWidth()) + 2;
					newY = w.getY();

					Poem p = board.poemThatContains(w);

					snappedToItem = p != null ? p.rowThatContains(w) : w;
					snappedToEdge = Edge.RIGHT;
				}else if(Math.abs(newX + item.getWidth() - w.getX()) <= 10){
					newX = (w.getX() - item.getWidth()) - 2;
					newY = w.getY();

					Poem p = board.poemThatContains(w);

					snappedToItem = p != null ? p.rowThatContains(w) : w;
					snappedToEdge = Edge.LEFT;
				}
			}

			if(w != item && (newX + item.getWidth()) >= w.getX() && newX < w.getX() + w.getWidth()){
				if(Math.abs(newY - (w.getY() + w.getHeight())) <= 10){
					newY = (w.getY() + w.getHeight()) + 2;

					Poem p = board.poemThatContains(w);
					Row r = p != null ? p.rowThatContains(w) : null;

					if(p != null && p.getRows().indexOf(r) < p.getRows().size() - 1){
						snappedToItem = p.nextRow(r);

						if(item.getX() >= snappedToItem.getX() + snappedToItem.getWidth()){
							newX = snappedToItem.getX() + snappedToItem.getWidth() + 2;
							snappedToEdge = Edge.RIGHT;
						}else if(item.getX() + item.getWidth() <= snappedToItem.getX()){
							newX = (snappedToItem.getX() - item.getWidth()) - 2;
							snappedToEdge = Edge.LEFT;
						}
					}else{
						snappedToItem = p != null ? p : w;
						snappedToEdge = Edge.BOTTOM;
					}
				}else if(Math.abs(newY + item.getHeight() - w.getY()) <= 10){
					newY = (w.getY() - item.getHeight()) - 2;

					Poem p = board.poemThatContains(w);
					Row r = p != null ? p.rowThatContains(w) : null;

					if(p != null && p.getRows().indexOf(r) > 0){
						snappedToItem = p.previousRow(r);

						if(item.getX() >= snappedToItem.getX() + snappedToItem.getWidth()){
							newX = snappedToItem.getX() + snappedToItem.getWidth() + 2;
							snappedToEdge = Edge.RIGHT;
						}else if(item.getX() + item.getWidth() <= snappedToItem.getX()){
							newX = (snappedToItem.getX() - item.getWidth()) - 2;
							snappedToEdge = Edge.LEFT;
						}
					}else{
						snappedToItem = p != null ? p : w;
						snappedToEdge = Edge.TOP;
					}
				}
			}

			snapped = snappedToItem != null;

			if(snapped){
				item.setPosition(newX, newY);
			}
		}

		return snapped;
	}
	/**
	 * 
	 */
	public void click(int x, int y, int clicks){
		Movable clickedItem = board.getMovableAt(x, y, SelectionMode.DEFAULT);
		
		if (clicks == 2 && clickedItem instanceof Poem) {
			int dialogResult = JOptionPane.showConfirmDialog (null, "Would you like to publish the selected Poem?", "Warning", JOptionPane.YES_NO_OPTION);
			
			if(dialogResult == JOptionPane.YES_OPTION){
				PublishController pc = new PublishController(gui, board);
				pc.publish((Poem) clickedItem);
				
				board.release(clickedItem);
			}
		}
	}

	/**
	 * Get an Item.
	 */
	public Movable getItem() {
		return item;
	}

	/**
	 * Press the mouse.
	 */
	public void mousePressed(MouseEvent e) {
		if(e.getClickCount() == 1){
			SelectionMode selectionMode = SelectionMode.DEFAULT;

			if(e.getModifiers() == 18){ //CTRL PRESSED
				selectionMode = SelectionMode.ONLY_ROWS;
			}else if(e.getModifiers() == 19){ //CTRL + SHIFT PRESSED
				selectionMode = SelectionMode.ONLY_WORDS;
			}

			select(e.getX(), e.getY(), selectionMode);
		}
	}

	/**
	 * Drag with mouse. 
	 */
	public void mouseDragged(MouseEvent e) {
		drag(e.getX(), e.getY());
		gui.getCanvas().repaint();
	}

	/**
	 * Move the mouse.
	 */
	public void mouseMoved(MouseEvent e) {
		if(e.getY() > Constants.PROTECTED_AREA_HEIGHT){
			Word hoveredWord = (Word) board.getMovableAt(e.getX(), e.getY(), SelectionMode.DEFAULT);

			if(hoveredWord != null){				
				board.bringWordToFront(hoveredWord);
				gui.updateTable(board.getUnprotectedWords());
				gui.getCanvas().repaint();
				return;
			}
		}

		if(item != null){
			drag(e.getX(), e.getY());
			gui.getCanvas().repaint();
		}
	}

	/**
	 * Release the mouse.
	 */
	public void mouseReleased(MouseEvent e) {
		release(e.getX(), e.getY());
		gui.getCanvas().repaint();
		item = null;
	}

	/**
	 * Mouse click. 
	 */
	public void mouseClicked(MouseEvent e) {
		click(e.getX(), e.getY(), e.getClickCount());
	}

	public void mouseEntered(MouseEvent e) {
	}

	public void mouseExited(MouseEvent e) {
	}
}
